import json
from functools import wraps
from typing import Dict, Type, TypeVar, Union

from httpx import Response
from pydantic import Extra
from pydantic.main import BaseModel

from apitest.engine.const.enums import Method, RType
from apitest.engine.logging import logger

T = TypeVar("T", bound=BaseModel)

DEFAULT_METHOD_REQUEST_KEY_MAP = {
    Method.GET: "params",
    Method.POST: "content",
    Method.PUT: "content",
    Method.DELETE: "content",
}


def parse_request(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        parse_key = ["params", "json", "data"]
        for key in parse_key:
            if kwargs.get(key) is not None and isinstance(kwargs[key], BaseModel):
                kwargs[key].Config.use_enum_values = True
                kwargs[key] = kwargs[key].dict(exclude_none=True)
        return func(*args, **kwargs)

    return wrapper


def build_request(method: Method):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):

            def obj_2_dict(val):
                if isinstance(val, BaseModel):
                    val.Config.use_enum_values = True
                    return val.dict(exclude_none=True)
                return val

            kwargs = {key: obj_2_dict(value) for key, value in kwargs.items()}
            request_param = kwargs.pop("req", None)
            if request_param is not None and DEFAULT_METHOD_REQUEST_KEY_MAP.get(method) is not None:
                kwargs[DEFAULT_METHOD_REQUEST_KEY_MAP[method]] = request_param
            kwargs = {key: json.dumps(value, ensure_ascii=False) if key == "content" else value for key, value in
                      kwargs.items() if value is not None}

            return func(*args, **kwargs)

        return wrapper

    return decorator


def build_response(
        resp: Response, model: Type[T] = None, rtype: RType = RType.obj, extra: Extra = Extra.ignore
) -> Union[dict, Response, T]:
    """
    将request请求返回数据转换成目标类型
    :param resp: request请求原始返回
    :param model: 需要解析的目标BaseModel类
    :param rtype: 返回类型，包含：RType.obj、RType.json和RType.raw
    :param extra: 额外参数，包含：Extra.ignore、Extra.allow和Extra.forbid
    :return: Dict[RType, Union[dict, Response, T]]
    """
    if resp.status_code != 200:
        logger.error("request error, response code: {}".format(resp.status_code))
        return resp

    if rtype is RType.raw:
        return resp
    result = resp.json()
    if rtype is RType.json:
        return result
    elif rtype is RType.obj:
        model.Config.extra = extra
        model.update_forward_refs()
        result = model(**result)
        return result


def parse_response(
        resp: Response, model: Type[T] = None, rtype: RType = RType.obj
) -> Dict[RType, Union[dict, Response, T]]:
    """
    将request请求返回数据转换成目标类型
    :param resp: request请求原始返回
    :param model: 需要解析的目标BaseModel类
    :param rtype: 返回类型，包含：RType.obj、RType.json和RType.raw
    :return: Dict[RType, Union[dict, Response, T]]
    """
    result = build_response(resp, model, rtype)
    return result
